前面部署 App 時有用到 Secret 來儲存資料庫的敏感資料,例如登入帳號、密碼等等。ConfigMap 也是類似的用途,可以拿來存放設定檔。今天就來介紹這兩種 object。
建立 ConfigMap 的 yaml 範例如下:
apiVersion: v1
kind: ConfigMap
metadata:
	name: app-config
data:
	APP_COLOR: blue
	APP_MODE: prod
想要用指令建立則使用 kubectl create configmap ...
# kubectl create configmap <config-name> --from-literal=<key>=<value>
kubectl create configmap \
	app-config --from-literal=APP_COLOR=blue --from-literal=APP_MOD=prod
如果想要從檔案建立也行
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt \ 
	--from-file=key2=/path/to/bar/file2.txt
kubectl create configmap my-config --from-file=path/to/bar
詳細的 options / flags → https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-configmap-em-
要怎麼在 Pod 中引用呢?有三種方式:
利用 envFrom
apiVersion: v1
kind: Pod
metadata:
  name: demo-1
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      envFrom:
        - configMapRef:
            name: app-config #app-config - 前面先建好的 configmap name
用 envFrom 的方式可以直接 reference 整個 configMap。用 printenv 來看看是不是真的設定好了我們定義的 key-value pair
利用 env
apiVersion: v1
kind: Pod
metadata:
  name: demo-2
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      env:
        - name: APP_COLOR_NEW
          valueFrom:
            configMapKeyRef:
              name: app-config #configMap name
              key: APP_COLOR #這邊的 key 要是 ConfigMap 裡面使用的 key name
env 則是可以分別定義單一個環境變數,注意這邊 env name 與 configMap 定義的名字不同。
利用 volume
apiVersion: v1
kind: Pod
metadata:
  name: demo-3
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      volumeMounts:
        - name: app-config-volume #volume's name, defined below
          mountPath: "/config"
          readOnly: true
  volumes:
    - name: app-config-volume
      configMap:
        name: app-config #app-config - 前面先建好的 configmap name
使用 Volume 則是讓所有在 Pod 中的 container 都能使用,這個方式會將 configMap 定義的 key 轉成一個一個檔案。
ConfigMap 除了 key-value 的形式外,也有另一種方式來做設置檔。有比較多、比較複雜的配置的時候可以使用。
apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # property-like keys; each key maps to a simple value
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"
  # file-like keys
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true
使用 file-like keys 的時候,用 envFrom 的方式最後得到的變數會不如預期。這時使用 volumes 的方式去掛 configMap 才能讀到正確的內容。demo 如下:
apiVersion: v1
kind: Pod
metadata:
  name: test-1
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      envFrom:
        - configMapRef:
            name: game-demo
---
apiVersion: v1
kind: Pod
metadata:
  name: test-2
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      volumeMounts:
        - name: config
          mountPath: "/config"
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: game-demo
        # An array of keys from the ConfigMap to create as files
        items:
          - key: "game.properties"
            path: "game.properties"
          - key: "user-interface.properties"
            path: "user-interface.properties"
apply pod 後進去容器中看看
test-1
印出了一些…不是我們想要的東西,使用上變得比較麻煩
test-2
如果是用 volume 掛載的方式,則會產生檔案,檔案中會完整保留 configMap 設置的值。因 items 的部分設置了兩個 key,所以這邊產生了兩個檔案。如果 items 的部分不填,就會看原本 configMap 中定義多少 key,全部都會轉成檔案。以這邊的例子來說 game-demo 這個 configMap 則會產生四個檔案。
也是有 Imperative way & Declarative way 來建立。
kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-file=ssh-publickey=path/to/id_rsa.pub
apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin # required field for kubernetes.io/basic-auth
  password: t0p-Secret # required field for kubernetes.io/basic-auth
Secret type 可參考官方文件 → https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
一般使用比較熟悉的是 Opaque ,如果不指定 type 預設也會是這個種類。之前部署時底下內容是用 data field,必須是 base64-encoded 字串。這邊用 stringData 就會是明文啦。
Pod 使用 Secret 的方式與 ConfigMap 相同,可以用三種方式引用:
利用 envFrom
apiVersion: v1
kind: Pod
metadata:
  name: demo-1
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      envFrom:
        - secretRef:
            name: app-secret
利用 env
apiVersion: v1
kind: Pod
metadata:
  name: demo-2
spec:
  containers:
    - name: nginx
      image: nginx:1.24.0-alpine3.17
      ports:
        - containerPort: 80
      env:
        - name: DB_PWD
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: DB_PASSWORD
利用 volumes
apiVersion: v1
kind: Secret
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo= # -> it will become decoded string inside the container
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-dotfiles-pod
spec:
  containers:
    - name: dotfile-test-container
      image: nginx
      volumeMounts:
        - name: secret-volume
          readOnly: true
          mountPath: "/etc/secret-volume"
	volumes:
    - name: secret-volume
      secret:
        secretName: dotfile-secret

可以執行看看 echo -n "dmFsdWUtMg0KDQo=" | base64 --decode 出來的結果是不是跟上圖相同
過去在開發程式的時候都是使用 .env 的檔案來做環境配置或儲存 API key 等這類機密資料,轉換到 kubernetes 還是不太能掌握實際生產環境會怎麼去做這個設置。曾看到有一個做法是在部署的時候去讀取本機 .env 檔案內的資料,再用 kubectl create secret generic ... 的方式建立 Secret 。現在還有別種方式來管理這些機密資料,例如 Helm Secrets, HashiCorp Vault。
Reference
https://kubernetes.io/docs/concepts/configuration/configmap/
https://kubernetes.io/docs/concepts/configuration/secret/